package com.mindata.blockchain.core.manager;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Lists;
import com.mindata.blockchain.block.Block;
import com.mindata.blockchain.block.Instruction;
import com.mindata.blockchain.block.InstructionInfo;
import com.mindata.blockchain.block.db.DbStore;
import com.mindata.blockchain.common.Constants;
import com.mindata.blockchain.common.FastJsonUtil;
import com.mindata.blockchain.common.ResultPack;
import com.mindata.blockchain.core.vo.InstructionVo;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author wuweifeng wrote on 2018/3/13.
 */
@Service
public class DbBlockManager {
    @Resource
    private DbStore dbStore;

    /**
     * 查找第一个区块
     *
     * @return 第一个Block
     */
    public Block getFirstBlock() {
        String firstBlockHash = dbStore.get(Constants.KEY_FIRST_BLOCK);
        if (StrUtil.isEmpty(firstBlockHash)) {
            return null;
        }
        return getBlockByHash(firstBlockHash);
    }

    /**
     * 获取最后一个区块信息
     *
     * @return 最后一个区块
     */
    public Block getLastBlock() {
        String lastBlockHash = dbStore.get(Constants.KEY_LAST_BLOCK);
        if (StrUtil.isEmpty(lastBlockHash)) {
            return null;
        }
        return getBlockByHash(lastBlockHash);
    }

    /**
     * 获取最后一个区块的hash
     *
     * @return hash
     */
    public String getLastBlockHash() {
        Block block = getLastBlock();
        if (block != null) {
            return block.getHash();
        }
        return null;
    }

    /**
     * 获取最后一个block的number
     *
     * @return number
     */
    public long getLastBlockNumber() {
        Block block = getLastBlock();
        if (block != null) {
            return block.getBlockHeader().getNumber();
        }
        return 0;
    }

    /**
     * 获取某一个block的下一个Block
     *
     * @param block block
     * @return block
     */
    public Block getNextBlock(Block block) {
        if (block == null) {
            return getFirstBlock();
        }
        String nextHash = dbStore.get(Constants.KEY_BLOCK_NEXT_PREFIX + block.getHash());
        if (nextHash == null) {
            return null;
        }
        return getBlockByHash(nextHash);
    }

    public Block getNextBlockByHash(String hash) {
        if (hash == null) {
            return getFirstBlock();
        }
        String nextHash = dbStore.get(Constants.KEY_BLOCK_NEXT_PREFIX + hash);
        if (nextHash == null) {
            return null;
        }
        return getBlockByHash(nextHash);
    }

    public Block getBlockByHash(String hash) {
        String blockJson = dbStore.get(hash);
        return FastJsonUtil.toBean(blockJson, Block.class);
    }

    public Block getBlockByNumber(long blockNumber) {
        String hash = dbStore.get(Constants.KEY_BLOCK_NUMBER + blockNumber);
        if (StringUtils.isBlank(hash)) {
            return null;
        }
        Block block = getBlockByHash(hash);
        return block;
    }

    public String getCoinBasePubKey() {
        String pubKey = dbStore.get(Constants.COIN_BASE_PUB_KEY);
        return pubKey;
    }

    public void putCoinBasePubKey(String publicKey) {
        dbStore.put(Constants.COIN_BASE_PUB_KEY, publicKey);
    }

    /**
     * 根据指令hash获取指令内容
     *
     * @param hash
     * @return
     */
    public ResultPack<InstructionVo> getInstructionByHash(String hash) {
        InstructionInfo info = this.getInstructionInfoByHash(hash);
        if (info == null) {
            return ResultPack.failed("指令不存在");
        }
        Block block = this.getBlockByHash(info.getBlockHash());
        if (block == null) {
            return ResultPack.failed("指令对应Block不存在");
        }
        Instruction instruction = block.getBlockBody().getInstructions().get(info.getIndex());
        if (instruction == null) {
            return ResultPack.failed("指令对应Block中记录不存在");
        }
        InstructionVo inVo = new InstructionVo();
        BeanUtil.copyProperties(instruction, inVo);
        inVo.setBlockHash(block.getHash());
        inVo.setBlockNumber(block.getBlockHeader().getNumber());
        return ResultPack.succeed(inVo);
    }

    public InstructionInfo getInstructionInfoByHash(String hash) {
        String json = dbStore.get(Constants.KEY_INSTRUCTION_PREFIX + hash);
        return FastJsonUtil.toBean(json, InstructionInfo.class);
    }

    /**
     * 指令是否存在
     *
     * @param hash
     * @return
     */
    public boolean instructionExists(String hash) {
        InstructionInfo info = this.getInstructionInfoByHash(hash);
        return info != null;
    }

    /**
     * 根据本地指令过滤取出待打包交易列表
     *
     * @param instructions
     * @return
     */
    public List<Instruction> checkAndGetpendingInstructions(List<Instruction> instructions) {
        List<Instruction> list = Lists.newArrayList();
        if (CollUtil.isNotEmpty(instructions)) {
            instructions.forEach(e -> {
                if (!instructionExists(e.getHash())) {
                    list.add(e);
                }
            });
        }
        return list;
    }

    /**
     * 获取当前节点Coinbase
     *
     * @return
     */
    public String getCoinbase() {
        String pubKey = dbStore.get(Constants.COIN_BASE_PUB_KEY);
        return pubKey;
    }

    /**
     * 修改Coinbase
     *
     * @param publicKey
     * @return
     */
    public boolean updateCoinbase(String publicKey) {
        dbStore.put(Constants.COIN_BASE_PUB_KEY, publicKey);
        return true;
    }
}
